import xml.etree.ElementTree as XMLET
import os
import struct
import re
import pprint

import BinEditorInfo
import MapOps
import BinOps

from Logger import *


""" struct info
#define IIR_PARAM_NUM 20

typedef enum {
    IIR_TYPE_LOW_SHELF = 0,
    IIR_TYPE_PEAK,
    IIR_TYPE_HIGH_SHELF,
    IIR_TYPE_LOW_PASS,
    IIR_TYPE_HIGH_PASS,
    IIR_TYPE_NUM
} IIR_TYPE_T;

typedef struct {
    IIR_TYPE_T  type;
    float       gain;
    float       fc;
    float       Q;
} IIR_PARAM_T;

typedef struct {
    float   gain0;
    float   gain1;
    int     num;
    IIR_PARAM_T param[IIR_PARAM_NUM];
} IIR_CFG_T;
"""

# struct definition begin
IIR_PARAM_NUM = 20
IIR_CFG_T_SIZE = "0x14c"
IIR_CFG_T_UNPACK_FORMAT = "<ffi" + "ifff" * IIR_PARAM_NUM
IIR_TYPE_T_STR_TO_INT = {"IIR_TYPE_LOW_SHELF": "0",
                         "IIR_TYPE_PEAK": "1",
                         "IIR_TYPE_HIGH_SHELF": "2",
                         "IIR_TYPE_LOW_PASS": "3",
                         "IIR_TYPE_HIGH_PASS": "4",
                         "IIR_TYPE_NUM": "5"
                         }

IIR_TYPE_T_INT_TO_STR = ("IIR_TYPE_LOW_SHELF",
                         "IIR_TYPE_PEAK",
                         "IIR_TYPE_HIGH_SHELF",
                         "IIR_TYPE_LOW_PASS",
                         "IIR_TYPE_HIGH_PASS",
                         "IIR_TYPE_NUM"
                         )

"""config file format
const IIR_CFG_T audio_eq_hw_dac_iir_cfg_classics = {
    .gain0 = 0,
    .gain1 = 0,
    .num = 8,
    .param = {
        {IIR_TYPE_PEAK, -10.1,   100.0,   7},
        {IIR_TYPE_PEAK, -10.1,   400.0,   7},
        {IIR_TYPE_PEAK, -10.1,   700.0,   7},
        {IIR_TYPE_PEAK, -10.1,   1000.0,   7},
        {IIR_TYPE_PEAK, -10.1,   3000.0,   7},
        {IIR_TYPE_PEAK, -10.1,   5000.0,   7},
        {IIR_TYPE_PEAK, -10.1,   7000.0,   7},
        {IIR_TYPE_PEAK, -10.1,   9000.0,   7},

    }
};
"""

# config file RE PATTERN
FLOAT_RE_PATTERN = r"([-]*[0-9]+[.]*[0-9]*)"
INT_RE_PATTERN = r"([0-9]+)"
IIR_TYPE_T_RE_PATTERN = r"(IIR_TYPE_LOW_SHELF|IIR_TYPE_PEAK|IIR_TYPE_HIGH_SHELF|IIR_TYPE_LOW_PASS|IIR_TYPE_HIGH_PASS|IIR_TYPE_NUM)"

FLOAT_RE_MATCH_PATTERN = r"^" + FLOAT_RE_PATTERN + r"$"
INT_RE_MATCH_PATTERN = r"^" + INT_RE_PATTERN + r"$"
IIR_TYPE_T_RE_MATCH_PATTERN = r"^" + IIR_TYPE_T_RE_PATTERN + r"$"

LINE_2_4_COMMON_RE_PATTERN = r"^\s+\.{}\s*=\s*{}\s*,\s*$"
LINE_2_RE_MATCH_PATTERN = LINE_2_4_COMMON_RE_PATTERN.format("gain0", FLOAT_RE_PATTERN)
LINE_3_RE_MATCH_PATTERN = LINE_2_4_COMMON_RE_PATTERN.format("gain1", FLOAT_RE_PATTERN)
LINE_4_RE_MATCH_PATTERN = LINE_2_4_COMMON_RE_PATTERN.format("num", INT_RE_PATTERN)

LINE_PARAM_BRACKET_DATA_PATTERN = r"{}\s*,\s*{}\s*,\s*{}\s*,\s*{}\s*".format(IIR_TYPE_T_RE_PATTERN, FLOAT_RE_PATTERN, FLOAT_RE_PATTERN, FLOAT_RE_PATTERN)
LINE_PARAM_RE_MATCH_PATTERN = r"^\s+{\s*" + LINE_PARAM_BRACKET_DATA_PATTERN + r"\s*}\s*,\s*$"

FLOAT_RE_MATCH_PATTERN_COMPILED = re.compile(FLOAT_RE_MATCH_PATTERN)
INT_RE_MATCH_PATTERN_COMPILED = re.compile(INT_RE_MATCH_PATTERN)
IIR_TYPE_T_RE_MATCH_PATTERN_COMPILED = re.compile(IIR_TYPE_T_RE_MATCH_PATTERN)

LINE_1_RE_MATCH_PATTERN_COMPILED = re.compile(r"^const\s+IIR_CFG_T\s+\S+\s+=\s+{\s*$")
LINE_2_RE_MATCH_PATTERN_COMPILED = re.compile(LINE_2_RE_MATCH_PATTERN)
LINE_3_RE_MATCH_PATTERN_COMPILED = re.compile(LINE_3_RE_MATCH_PATTERN)
LINE_4_RE_MATCH_PATTERN_COMPILED = re.compile(LINE_4_RE_MATCH_PATTERN)
LINE_5_RE_MATCH_PATTERN_COMPILED = re.compile(r"^\s+\.param\s*=\s*{\s*$")
LINE_PARAM_RE_MATCH_PATTERN_COMPILED = re.compile(LINE_PARAM_RE_MATCH_PATTERN)
LINE_LAST_2_RE_MATCH_PATTERN_COMPILED = re.compile(r"^\s+}\s*$")
LINE_LAST_1_RE_MATCH_PATTERN_COMPILED = re.compile(r"^\s*};\s*$")

IIR_CFG_T_GAIN0_FIND_RE_PATTERN = r"\s+\.gain0\s*=\s*" + FLOAT_RE_PATTERN + r"\s*,\s*"
IIR_CFG_T_GAIN1_FIND_RE_PATTERN = r"\s+\.gain1\s*=\s*" + FLOAT_RE_PATTERN + r"\s*,\s*"
IIR_CFG_T_NUM_FIND_RE_PATTERN = r"\s+\.num\s*=\s*" + INT_RE_PATTERN + r"\s*,\s*"
IIR_CFG_T_PARAM_FIND_RE_PATTERN = LINE_PARAM_RE_MATCH_PATTERN

IIR_CFG_T_GAIN0_FIND_RE_PATTERN_COMPILED = re.compile(IIR_CFG_T_GAIN0_FIND_RE_PATTERN)
IIR_CFG_T_GAIN1_FIND_RE_PATTERN_COMPILED = re.compile(IIR_CFG_T_GAIN1_FIND_RE_PATTERN)
IIR_CFG_T_NUM_FIND_RE_PATTERN_COMPILED = re.compile(IIR_CFG_T_NUM_FIND_RE_PATTERN)
IIR_CFG_T_PARAM_FIND_RE_PATTERN_COMPILED = re.compile(IIR_CFG_T_PARAM_FIND_RE_PATTERN)


"""struct list format, digits format (not string)
    [
        gain0,
        gain1,
        num,
        [
            [type, gain, fc, Q], # param[0]
            [type, gain, fc, Q], # param[1]
            ...
            [type, gain, fc, Q], # param[IIR_PARAM_NUM - 1]
        ]
    ]
"""
STRUCT_LIST_FILL_DATA = [0, 0.00, 0.00, 0.00]
FLOAT_POINT_PRECISION = 2


class EqEditor(object):
    def __init__(self) -> None:
        pass

    def read_from_bin(self,
                      bin_filepath: str,
                      map_filepath: str,
                      project_name: str,
                      eq_name: str = None,
                      eq_index: str = None
                      ) -> list:
        """Read struct list from app bin, return a struct list format

        Args:
            bin_filepath (str): The full path of app bin.
            map_filepath (str): The full path of map file.
            project_name (str): The project name for the app bin
            eq_name (str, optional): The eq name in config xml. Defaults to None.
            eq_index (str, optional): The eq index in config xml. Defaults to None.

        Returns:
            list | None: struct_list: See "struct list format".
        """
        if (not bin_filepath) or (not map_filepath) or (not project_name) or ((not eq_name) and (not eq_index)):
            LOG(LEVEL_WARN, "Read from bin [Failed], param ivalid."
                "bin: [{}], map: [{}], project_name: [{}], eq_name: [{}], eq_index: [{}]".format(
                    bin_filepath,
                    map_filepath,
                    project_name,
                    eq_name,
                    eq_index
                ))
            return None
        struct_addr_start, struct_addr_stop = self.get_sturct_info_from_map(map_filepath, project_name, eq_name, eq_index)
        if not struct_addr_start or not struct_addr_stop:
            LOG(LEVEL_WARN, "Read from bin [Failed]. Cannot get info from map. \
                    map: [{}], project_name: [{}], eq_name: [{}], eq_index: [{}]".format(
                map_filepath,
                project_name,
                eq_name,
                eq_index
            ))
            return None
        struct_bytes = BinOps.get_bytes_dec(bin_filepath, struct_addr_start, struct_addr_stop)
        if not struct_bytes:
            LOG(LEVEL_WARN, "Read from bin [Failed], get bytes from bin failed. \
                    bin: [{}], project_name: [{}], eq_name: [{}], eq_index: [{}]".format(
                bin_filepath,
                project_name,
                eq_name,
                eq_index
            ))
            return None
        LOG(LEVEL_INFO, "Read from bin: start: [{}][{}], stop: [{}][{}], len: [{}]".format(
            struct_addr_start,
            hex(struct_addr_start),
            struct_addr_stop,
            hex(struct_addr_stop),
            len(struct_bytes)
        ))
        for index in range(0, len(struct_bytes), 16):
            start = index
            stop = index + 16
            pprint.pprint(struct_bytes[start: stop])

        struct_data = self.bytes_unpack(struct_bytes)
        if struct_data and self._check_struct_list_is_valid(struct_data):
            LOG(LEVEL_INFO, "Read struct info from bin [Successful].")
            pprint.pprint(struct_data)
            return struct_data
        else:
            LOG(LEVEL_WARN, "Read struct info from bin [Failed].")
            return None

    def write_to_bin(self,
                     struct_list: list,
                     bin_filepath_old: str,
                     bin_filepath_new: str,
                     map_filepath: str,
                     project_name: str,
                     eq_name: str = None,
                     eq_index: str = None
                     ) -> bytes:
        """_summary_

        Args:
            struct_list (list): The data of struct list format
            bin_filepath_old (str): The full path of the App bin which you want to change.
            bin_filepath_new (str): The full path of the App bin which you want to write.
            map_filepath (str): The full path of the map path alone with the App bin.
            project_name (str): The project name for the app bin.
            eq_name (str, optional): The eq name in config xml. Defaults to None.
            eq_index (str, optional): The eq index in config xml. Defaults to None.

        Returns:
            bytes | None: return the packed bytes from struct list
        """
        if not self._check_struct_list_is_valid(struct_list):
            LOG(LEVEL_WARN, "Write to bin [Failed]. struct_list format invalid.")
            pprint.pprint(struct_list)
            return None
        struct_addr_start, struct_addr_stop = self.get_sturct_info_from_map(map_filepath, project_name, eq_name, eq_index)
        if not struct_addr_start or not struct_addr_stop:
            LOG(LEVEL_WARN, "Write to bin [Failed]. Cannot get info from map. \
                    map: [{}], project_name: [{}], eq_name: [{}], eq_index: [{}]".format(
                map_filepath,
                project_name,
                eq_name,
                eq_index
            ))
            return None
        struct_bytes = self.bytes_pack(struct_list)
        if not struct_bytes:
            LOG(LEVEL_WARN, "Write to bin [Failed], bytes pack fail.")
            return None
        ret = BinOps.set_bytes_dec(struct_bytes, bin_filepath_old, bin_filepath_new, struct_addr_start, struct_addr_stop)
        if not ret:
            LOG(LEVEL_WARN, "Write to bin [Failed], bin ops set bytes dec failed.")
            return None
        LOG(LEVEL_INFO, "Write to bin [Successful], old_bin: [{}], new_bin: [{}]".format(bin_filepath_old, bin_filepath_new))
        return ret

    def read_from_config_file(self, filepath: str) -> list:
        """Read data from config file, then format it to struct_list format.

        Args:
            filepath (str): The full path of the config file.

        Returns:
            list: struct_list: See "struct list format".
        """
        if not filepath or not os.path.isfile(filepath):
            LOG(LEVEL_WARN, "Cannot read data from cfg file, filepath: [{}]".format(filepath))
            return None
        try:
            fd = open(filepath, "r", encoding="utf8")
            cfg_file_lines = fd.read().split("\n")
            fd.close()
        except Exception as e:
            LOG(LEVEL_WARN, "The config file cannot open or read, please check. filepath: [{}], Exception: [{}]".format(filepath, str(e)))
            return None

        if not self._check_cfg_file_is_valid(cfg_file_lines):
            LOG(LEVEL_WARN, "Cannot read data from cfg file, file data invalid. please check. filepath: [{}]".format(filepath))
            return None

        struct_list = self._format_cfg_file_data_to_list(cfg_file_lines)
        if not struct_list:
            LOG(LEVEL_WARN, "Cannot format cfg file data, please check detail.")
            return None
        LOG(LEVEL_INFO, "Read from config file [Successful].")
        struct_list = self._fill_struct_list(struct_list)
        if not struct_list:
            LOG(LEVEL_WARN, "Read from config file [Failed], fill struct_list failed.")
            return None
        return struct_list

    def write_to_config_file(self, struct_list: list, filepath: str, project_name: str, eq_name: str = None, eq_index: str = None) -> list:
        """Format struct list to config file format, then write it to a file.

        Args:
            struct_list (list): The struct list which you want format and write to the config file.
            filepath (str): The full path of the config file.
            eq_name (str): The eq name in config xml. Defaults to None.
            eq_index (str): The eq index in config xml. Defaults to None.

        Returns:
            list | None: The lines data of the config file.
        """
        if (not filepath) or (not struct_list) or (not project_name) or ((not eq_name) and (not eq_index)):
            LOG(LEVEL_WARN, "Cannot write struct data to config file, given param error. path: [{}], eq_name: [{}], eq_index: [{}].".format(
                filepath,
                eq_name,
                eq_index
            ))
            return None

        if eq_name:
            struct_name = self.get_struct_name_by_eq_name(eq_name, project_name)
        elif eq_index:
            struct_name = self.get_struct_name_by_index(eq_index, project_name)
        else:
            struct_name = None
        if not struct_name:
            LOG(LEVEL_WARN, "Write to config file [Failed], struct_name convert fail. path: [{}], eq_name: [{}], eq_index: [{}].".format(
                filepath,
                eq_name,
                eq_index
            ))
            return None
        pprint.pprint(struct_list)
        if not self._check_struct_list_is_valid(struct_list):
            LOG(LEVEL_WARN, "Write to config file [Failed], struct_list format error")
            return None
        cfg_file_lines = self._format_struct_list_to_cfg_file_data(struct_list, struct_name, project_name, eq_name)
        if not cfg_file_lines:
            LOG(LEVEL_WARN, "Write to config file [Failed], cfg_file_lines format error")
            return None
        try:
            fd = open(filepath, "w", encoding="utf8")
            for line in cfg_file_lines:
                fd.write(line + "\n")
            fd.close()
        except Exception as e:
            LOG(LEVEL_WARN, "Write to config file [Failed], cannot open or write filepath: [{}], Exception: [{}].".format(filepath, str(e)))
            return None
        LOG(LEVEL_INFO, "Write to config file [Successful].")
        pprint.pprint(cfg_file_lines)
        return cfg_file_lines

    def bytes_pack(self, struct_list: list) -> bytes:
        """Pack struct list to bytes

        Args:
            struct_list (list): See "struct list format".

        Returns:
            bytes: The bytes format from struct list.
        """
        if not self._check_struct_list_is_valid(struct_list):
            LOG(LEVEL_WARN, "Bytes pack [Failed], struct list format invalid.")
            return None
        if not struct_list:
            LOG(LEVEL_WARN, "Bytes pack [Failed], fill struct_list failed.")
            return None
        gain0_byte = struct.pack('<f', struct_list[0])
        gain1_bytes = struct.pack('<f', struct_list[1])
        num_bytes = struct.pack('<i', struct_list[2])
        struct_bytes = gain0_byte + gain1_bytes + num_bytes
        struct_list = self._fill_struct_list(struct_list)
        params = struct_list[3]
        for param in params:
            struct_bytes += struct.pack('<ifff', param[0], param[1], param[2], param[3])
        if len(struct_bytes) != int(IIR_CFG_T_SIZE, base=16):
            LOG(LEVEL_WARN, "Bytes pack [Failed], bytes length error. found: [{}], need: [{}]".format(
                len(struct_bytes, int(IIR_CFG_T_SIZE, base=16))))
            return None
        return struct_bytes

    def bytes_unpack(self, struct_bytes: bytes) -> list:
        """Unpack the struct bytes to struct list format.

        Args:
            struct_bytes (bytes): The bytes data of the struct list

        Returns:
            list: struct_list: See "struct list format".
        """
        if not struct_bytes:
            LOG(LEVEL_WARN, "Cannot unpack struct bytes data, data is None.")
            return None
        unpack_data = struct.unpack(IIR_CFG_T_UNPACK_FORMAT, struct_bytes)
        struct_list = []
        offset = 0
        struct_list.append(round(unpack_data[0], FLOAT_POINT_PRECISION)) # gain0
        struct_list.append(round(unpack_data[1], FLOAT_POINT_PRECISION)) # gain1
        struct_list.append(unpack_data[2]) # num
        offset = 3
        param = []
        for index in range(0, IIR_PARAM_NUM):
            type = unpack_data[offset]
            offset += 1
            gain = round(unpack_data[offset], FLOAT_POINT_PRECISION)
            offset += 1
            fc = round(unpack_data[offset], FLOAT_POINT_PRECISION)
            offset += 1
            Q = round(unpack_data[offset], FLOAT_POINT_PRECISION)
            offset += 1
            param.append([type, gain, fc, Q])
        struct_list.append(param)
        struct_list = self._fill_struct_list(struct_list)
        if not struct_list:
            LOG(LEVEL_WARN, "Bytes unpack [Failed], fill struct_list failed.")
            return None
        return struct_list

    def get_valid_eq_config(self, project_name: str) -> list:
        """Get the valid configuration of the specified project.

        Args:
            project_name (str): The project name which need to findout the config.

        Returns:
            list: The configuration list.
            [
                {"eq_name": eq_name, "struct_name": struct_name, "index": eq_index}
                {"eq_name": eq_name, "struct_name": struct_name, "index": eq_index}
                {"eq_name": eq_name, "struct_name": struct_name, "index": eq_index}
                ...
            ]
        """
        config_path = BinEditorInfo.CONFIG_FILE_ROOT_PATH
        xml_filename = BinEditorInfo.CONFIG_XML_FILENAME_EQ
        project_name = project_name
        xml_path = os.path.join(config_path, project_name, xml_filename)
        valid_eq_config = []
        if not os.path.isfile(xml_path):
            LOG(LEVEL_WARN, "Get valid config [Failed], no xml found in project: [{}], file: [{}]".format(
                project_name,
                xml_path
            ))
            return None
        xml_data = XMLET.parse(xml_path)
        root = xml_data.getroot()
        project = root.attrib.get("project")
        chip_vendor = root.attrib.get("chip_vendor")
        if (project != project_name) or (chip_vendor != "bes"):
            LOG(LEVEL_WARN, "Get valid config [Failed], project or chip not support. project found: [{}], need: [{}], chip vendor found: [{}], support: [{}]".format(
                project,
                project_name,
                chip_vendor,
                "bes"
            ))
            return None
        for child in root:
            if child.tag:
                eq_name = child.tag
                eq_variable_name = child.text
                eq_index = child.attrib.get("index")
                valid_eq_config.append({"eq_name": eq_name, "struct_name": eq_variable_name, "index": eq_index})
        if len(valid_eq_config) > 0:
            LOG(LEVEL_INFO, "Get valid config [Successful]")
            pprint.pprint(valid_eq_config)
            return valid_eq_config
        else:
            LOG(LEVEL_WARN, "Get valid config [Failed], no config found in xml.")
            return None

    def get_valid_project_config(self) -> dict:
        """Get valid project name and configs in CONFIG_FILE_ROOT_PATH.

        Returns:
            dict: project name and configs.
            {
                # eq_cfg is a list return from self.get_valid_eq_config
                project_name: eq_cfg,
                project_name: eq_cfg,
                ...
            }
        """
        config_path = BinEditorInfo.CONFIG_FILE_ROOT_PATH
        dir_items = os.listdir(config_path)
        valid_project = {}
        if not dir_items:
            LOG(LEVEL_WARN, "Get valid project [Failed], no dir found in [{}]".format(config_path))
            return None
        for item in dir_items:
            item_path = os.path.join(config_path, item)
            if not os.path.isdir(item_path):
                continue
            eq_cfg = self.get_valid_eq_config(item)
            if not eq_cfg:
                continue
            valid_project[item] = eq_cfg
        if not valid_project:
            LOG(LEVEL_WARN, "Get valid project [Failed], no valid project found for bes: [{}]".format(config_path))
            return None
        else:
            LOG(LEVEL_INFO, "Get valid project [Successful].")
            pprint.pprint(valid_project)
            return valid_project

    def get_sturct_info_from_map(self,
                              map_filepath: str,
                              project_name: str,
                              eq_name: str = None,
                              eq_index: str = None
                              ) -> int:
        """Get sturct info from map file. (such as the start & stop addr of the struct in the app bin.)

        Args:
            map_filepath (str): The full path of map file.
            project_name (str): The project name which to findout the struct.
            eq_name (str, optional): The eq name you want to find. Defaults to None.
            eq_index (str, optional): The eq index you want to find. Defaults to None.

        Returns:
            int, int: start_addr, stop_addr
        """
        if eq_name:
            struct_name = self.get_struct_name_by_eq_name(eq_name, project_name)
        elif eq_index:
            struct_name = self.get_struct_name_by_index(eq_index, project_name)
        else:
            struct_name = None
        if not struct_name:
            LOG(LEVEL_WARN, "Cannot find eq_name:[{}] or eq_index:[{}] in the project: [{}]".format(eq_name, eq_index, project_name))
            return None

        struct_addr, struct_size = self._get_eq_struct_info(struct_name, map_filepath)
        if struct_size != IIR_CFG_T_SIZE:
            LOG(LEVEL_WARN,
                "The struct size found in the map does not match the supported size, please check. map: [{}], name: [{}], found: [{}], supported: [{}]".format(
                map_filepath,
                struct_name,
                struct_size,
                IIR_CFG_T_SIZE
            ))
            return None
        app_bin_start_addr = self.get_app_start_addr(map_filepath)
        if not app_bin_start_addr:
            LOG(LEVEL_WARN,
                "The app start addr found in the map error, please check. map: [{}], found: [{}]".format(
                map_filepath,
                app_bin_start_addr,
            ))
            return None
        struct_addr_start = int(struct_addr, base=16) - int(app_bin_start_addr, base=16)
        struct_addr_stop = struct_addr_start + int(struct_size, base=16)
        LOG(LEVEL_INFO, "Get bin info from map [Successful]. start: [{}], stop: [{}], map: [{}], project_name: [{}], struct_name: [{}], eq_name: [{}], eq_index: [{}]".format(
                    struct_addr_start,
                    struct_addr_stop,
                    map_filepath,
                    project_name,
                    struct_name,
                    eq_name,
                    eq_index
                ))
        return struct_addr_start, struct_addr_stop

    def iir_type_t_convert_string_to_int(self, type_str: str) -> int:
        """Convert string to IIR_TYPE_T digit

        Args:
            type_str (str): The string of the IIR_TYPE_T enum menber.

        Returns:
            int: The digit of IIR_TYPE_T member.
        """
        if type_str:
            type_int = IIR_TYPE_T_STR_TO_INT.get(type_str)
            if type_int:
                return int(type_int)
        return None

    def iir_type_t_convert_int_to_str(self, type_int: int) -> str:
        """Convert IIR_TYPE_T digit enum member to string.

        Args:
            type_int (int): The digit of the IIR_TYPE_T enum member.

        Returns:
            str: The string of the IIR_TYPE_T enum member.
        """
        if (isinstance(type_int, int)) and (0 <= type_int) and (type_int <= len(IIR_TYPE_T_INT_TO_STR) - 1):
            return IIR_TYPE_T_INT_TO_STR[type_int]
        return None

    def get_struct_name_by_index(self, index: str, project_name):
        eq_config = self.get_valid_eq_config(project_name)
        if not eq_config:
            LOG(LEVEL_WARN, "Cannot find struct name by index for the project, eq_config error. project: [{}], index: [{}]".format(
                project_name,
                index
            ))
            return None
        for config in eq_config:
            if config.get("index") == index:
                LOG(LEVEL_INFO, "Found struct name by index: project: [{}], index: [{}], struct_name: [{}]".format(
                    project_name,
                    index,
                    config.get("struct_name")
                ))
                return config.get("struct_name")
        LOG(LEVEL_WARN, "Cannot find struct name by index for the project, config not found. project: [{}], index: [{}], supported config: [{}]".format(
            project_name,
            index,
            eq_config
        ))
        return None

    def get_struct_name_by_eq_name(self, eq_name: str, project_name):
        eq_config = self.get_valid_eq_config(project_name)
        if not eq_config:
            LOG(LEVEL_WARN, "Cannot find struct name by eq_name for the project, eq_config error. project: [{}], eq_name: [{}]".format(
                project_name,
                eq_name
            ))
            return None
        for config in eq_config:
            if config.get("eq_name") == eq_name:
                LOG(LEVEL_INFO, "Found struct name by eq_name: project: [{}], eq_name: [{}], struct_name: [{}]".format(
                    project_name,
                    eq_name,
                    config.get("struct_name")
                ))
                return config.get("struct_name")
        LOG(LEVEL_WARN, "Cannot find struct name by eq_name for the project, config not found. project: [{}], eq_name: [{}], supported config: [{}]".format(
            project_name,
            eq_name,
            eq_config
        ))
        return None

    def get_app_start_addr(self, map_filepath):
        addr = MapOps.get_bin_start_addr(map_filepath)
        if addr:
            LOG(LEVEL_INFO, "Found app start addr. file: [{}], addr: [{}]".format(map_filepath, addr))
            return addr
        else:
            LOG(LEVEL_WARN, "Cannot find app start addr in given map file: [{}]".format(map_filepath))
            return None

    def _get_eq_struct_info(self, struct_name, map_filepath):
        addr, size = MapOps.get_variable_info(map_filepath, struct_name)
        if addr and size:
            LOG(LEVEL_INFO, "Found eq struct info. file: [{}], struct: [{}], addr: [{}], size: [{}]".format(
                map_filepath,
                struct_name,
                addr,
                size))
            return addr, size
        else:
            LOG(LEVEL_WARN, "Cannot find eq_struct info in given map file. file: [{}] struct_name: [{}]".format(map_filepath, struct_name))
            return None

    def _check_struct_list_is_valid(self, struct_list):
        if not struct_list:
            return False
        if len(struct_list) != 4:
            return False
        if len(struct_list[3]) < 1 or len(struct_list[3]) > IIR_PARAM_NUM:
            return False
        if ((not FLOAT_RE_MATCH_PATTERN_COMPILED.match(str(struct_list[0]))) or
            (not isinstance(struct_list[0], float)) or
            (not FLOAT_RE_MATCH_PATTERN_COMPILED.match(str(struct_list[1]))) or
            (not isinstance(struct_list[1], float)) or
            (not INT_RE_MATCH_PATTERN_COMPILED.match(str(struct_list[2]))) or
            (not isinstance(struct_list[2], int))
            ):
            return False
        for param in struct_list[3]:
            if len(param) != 4:
                return False
            if ((not IIR_TYPE_T_RE_MATCH_PATTERN_COMPILED.match(self.iir_type_t_convert_int_to_str(param[0]))) or
                (not isinstance(param[0], int)) or
                (not FLOAT_RE_MATCH_PATTERN_COMPILED.match(str(param[1]))) or
                ((not isinstance(param[1], float)) and (not isinstance(param[1], int))) or
                (not FLOAT_RE_MATCH_PATTERN_COMPILED.match(str(param[2]))) or
                ((not isinstance(param[2], float)) and (not isinstance(param[2], int))) or
                (not FLOAT_RE_MATCH_PATTERN_COMPILED.match(str(param[3]))) or
                ((not isinstance(param[3], float)) and (not isinstance(param[3], int)))
            ):
                return False
        return True

    def _cfg_file_remove_unused_lines(self, lines):
        copyed_lines = []
        for line in lines:
            striped_line = line.strip()
            if not striped_line or striped_line.startswith("//"):
                continue
            copyed_lines.append(line)
        return copyed_lines

    def _check_cfg_file_is_valid(self, lines):
        copyed_lines = self._cfg_file_remove_unused_lines(lines)

        if len(copyed_lines) < 10:
            LOG(LEVEL_WARN, "Check config file return [{}], line count error. line count: [{}]".format(
                "False",
                len(copyed_lines)
            ))
            return False
        if not LINE_1_RE_MATCH_PATTERN_COMPILED.match(copyed_lines[0]):
            LOG(LEVEL_WARN, "Check config file return [{}], line[{}] format error. line data: [{}]".format(
                "False",
                "1",
                copyed_lines[0]
            ))
            return False
        if not LINE_2_RE_MATCH_PATTERN_COMPILED.match(copyed_lines[1]):
            LOG(LEVEL_WARN, "Check config file return [{}], line[{}] format error. line data: [{}]".format(
                "False",
                "2",
                copyed_lines[1]
            ))
            return False
        if not LINE_3_RE_MATCH_PATTERN_COMPILED.match(copyed_lines[2]):
            LOG(LEVEL_WARN, "Check config file return [{}], line[{}] format error. line data: [{}]".format(
                "False",
                "3",
                copyed_lines[2]
            ))
            return False
        if not LINE_4_RE_MATCH_PATTERN_COMPILED.match(copyed_lines[3]):
            LOG(LEVEL_WARN, "Check config file return [{}], line[{}] format error. line data: [{}]".format(
                "False",
                "4",
                copyed_lines[3]
            ))
            return False
        if not LINE_5_RE_MATCH_PATTERN_COMPILED.match(copyed_lines[4]):
            LOG(LEVEL_WARN, "Check config file return [{}], line[{}] format error. line data: [{}]".format(
                "False",
                "5",
                copyed_lines[4]
            ))
            return False
        if not LINE_LAST_2_RE_MATCH_PATTERN_COMPILED.match(copyed_lines[-2]):
            LOG(LEVEL_WARN, "Check config file return [{}], line[{}] format error. line data: [{}]".format(
                "False",
                "-2",
                copyed_lines[-2]
            ))
            return False
        if not LINE_LAST_1_RE_MATCH_PATTERN_COMPILED.match(copyed_lines[-1]):
            LOG(LEVEL_WARN, "Check config file return [{}], line[{}] format error. line data: [{}]".format(
                "False",
                "-1",
                copyed_lines[-1]
            ))
            return False
        param_line_count = 0
        for line_index in range(5, len(copyed_lines) - 3):
            line = copyed_lines[line_index]
            if not line.strip():
                continue
            else:
                if not LINE_PARAM_RE_MATCH_PATTERN_COMPILED.match(line):
                    LOG(LEVEL_WARN, "Check config file return [{}], line[{}] format error. line data: [{}]".format(
                        "False",
                        line_index + 1,
                        line
                    ))
                    return False
                else:
                    param_line_count += 1
        if param_line_count > int(IIR_PARAM_NUM):
            LOG(LEVEL_WARN, "Check config file return [{}], params count error, found: [{}], max: [{}]".format(
                "False",
                param_line_count,
                IIR_PARAM_NUM
            ))
            return False

        LOG(LEVEL_INFO, "Check config file [PASS].")
        return True

    def _format_cfg_file_data_to_list(self, raw_lines):
        """_summary_

        Args:
            raw_lines (_type_): _description_

        Returns:
            struct_list: See "struct list format".
        """
        lines = self._cfg_file_remove_unused_lines(raw_lines)
        gain0 = IIR_CFG_T_GAIN0_FIND_RE_PATTERN_COMPILED.findall(lines[1])
        gain1 = IIR_CFG_T_GAIN1_FIND_RE_PATTERN_COMPILED.findall(lines[2])
        num = IIR_CFG_T_NUM_FIND_RE_PATTERN_COMPILED.findall(lines[3])
        if (len(gain0) != 1) or (len(gain1) != 1) or (len(num) != 1):
            LOG(LEVEL_WARN, "Cannot format cfg file data, gain0/gain1/num error. gain0: [{}], gain1: [{}], num: [{}]".format(
                gain0, gain1, num
            ))
            return None
        gain0 = float(gain0[0])
        gain1 = float(gain1[0])
        num = int(num[0])
        params = []
        for line_num in range(5, len(lines) - 3):
            line = lines[line_num]
            if not line.strip():
                continue
            else:
                param = IIR_CFG_T_PARAM_FIND_RE_PATTERN_COMPILED.findall(line)
                if len(param) != 1:
                    LOG(LEVEL_WARN, "Cannot format cfg file data, param line format [Fail]. line_num: [{}], line:[{}].".format(
                        line_num + 1, line
                    ))
                    return None
                if len(param[0]) != 4:
                    LOG(LEVEL_WARN, "Cannot format cfg file data, param line format [Fail]. line_num: [{}], line:[{}].".format(
                        line_num + 1, line
                    ))
                    return None
                param_type = self.iir_type_t_convert_string_to_int(param[0][0])
                param_gain = float(param[0][1])
                param_fc = float(param[0][2])
                param_Q = float(param[0][3])
                params.append([param_type, param_gain, param_fc, param_Q])
        if params:
            struct_list = [gain0, gain1, num, params]
            pprint.pprint(struct_list)
            if not self._check_struct_list_is_valid(struct_list):
                LOG(LEVEL_WARN, "Format cfg file [Fail], struct_list format error.")
                return None
            LOG(LEVEL_INFO, "Format cfg file [Successful].")
            struct_list = self._fill_struct_list(struct_list)
            if not struct_list:
                LOG(LEVEL_WARN, "Format config file to struct list [Failed], fill struct_list failed.")
                return None
            return struct_list
        else:
            LOG(LEVEL_WARN, "Format cfg file [Fail], no param line found.")
            return None

    def _format_struct_list_to_cfg_file_data(self, struct_list, struct_name, project_name, eq_name):
        if not self._check_struct_list_is_valid(struct_list):
            LOG(LEVEL_WARN, "Cannot format struct list, list param error.")
            pprint.pprint(struct_list)
            return None
        lines = []
        lines.append("// project_name = [{}]".format(project_name))
        lines.append("// eq_name = [{}]".format(eq_name))
        lines.append("const IIR_CFG_T {} = ".format(struct_name) + "{")
        lines.append("{}.gain0 = {},".format(" " * 4, struct_list[0]))
        lines.append("{}.gain1 = {},".format(" " * 4, struct_list[1]))
        lines.append("{}.num = {},".format(" " * 4, struct_list[2]))
        lines.append("{}.param = ".format(" " * 4) + "{")
        for param in struct_list[3]:
            line = "{:>19},{:>10},{:>10},{:>10}".format(
                self.iir_type_t_convert_int_to_str(param[0]),
                param[1],
                param[2],
                param[3]
                )
            line = " " * 8 + "{" + line + "},"
            lines.append(line)
        lines.append(" " * 4 + "}")
        lines.append("};")
        if self._check_cfg_file_is_valid(lines):
            LOG(LEVEL_INFO, "Format struct list to cfg file data [Successfull]")
            pprint.pprint(lines)
            return lines
        LOG(LEVEL_WARN, "Format struct list to cfg file data [Failed]")
        pprint.pprint(lines)
        return None

    def _fill_struct_list(self, struct_list):
        if not self._check_struct_list_is_valid(struct_list):
            LOG(LEVEL_WARN, "Fill struct list [Failed], struct list format invalid.")
            return None
        params = struct_list[3]
        if len(params) == IIR_PARAM_NUM:
            return struct_list

        fill_count = IIR_PARAM_NUM - len(params)
        for index in range(0, fill_count):
            params.append(STRUCT_LIST_FILL_DATA)
        struct_list_new = [struct_list[0], struct_list[1], struct_list[2], params]
        return struct_list_new



def run_test():
    editor = EqEditor()
    bin_path = "/home/jimmy/code/tws_tools_10/bin_editor/test_files/ts6569a.bin"
    bin_path_new = "/home/jimmy/code/tws_tools_10/bin_editor/test_files/ts6569a.bin.new"
    map_path = "/home/jimmy/code/tws_tools_10/bin_editor/test_files/ts6569a.map"
    project_name = "ts6569a"
    eq_name = "经典原音"
    eq_index = "1"
    struct_name = "audio_eq_hw_dac_iir_cfg_classics"
    cfg_filepath = "/home/jimmy/code/tws_tools_10/bin_editor/{}.cfg".format(struct_name)
    cfg_filepath_write = "/home/jimmy/code/tws_tools_10/bin_editor/{}.cfg.new".format(struct_name)
    cfg_filepath_write2 = "/home/jimmy/code/tws_tools_10/bin_editor/{}.cfg.new2".format(struct_name)
    cfg_filepath_write3 = "/home/jimmy/code/tws_tools_10/bin_editor/{}.cfg.new3".format(struct_name)
    # struct_list = editor.read_from_config_file(cfg_filepath)
    # editor._format_struct_list_to_cfg_file_data(struct_list, struct_name)
    # editor.write_to_config_file(struct_list, struct_name, cfg_filepath_write)

    struct_list = editor.read_from_bin(bin_path, map_path, project_name, eq_name=eq_name)
    editor.write_to_config_file(struct_list, struct_name, cfg_filepath_write)
    struct_list[0] = 999.0
    struct_list[1] = -999.1
    struct_list[2] = 888
    struct_list[3][0][0] = 4
    struct_list[3][0][1] = -999.4
    struct_list[3][0][2] = 999.5
    struct_list[3][0][3] = -999.6
    struct_list[3][1][0] = 4
    struct_list[3][1][1] = -888.4
    struct_list[3][1][2] = 888.5
    struct_list[3][1][3] = -888.6
    struct_list[3][2][0] = 4
    struct_list[3][2][1] = -777.4
    struct_list[3][2][2] = 777.5
    struct_list[3][2][3] = -777.6
    editor.write_to_config_file(struct_list, struct_name, cfg_filepath_write2)
    editor.write_to_bin(struct_list, bin_path, bin_path_new, map_path, project_name, eq_name=eq_name)
    struct_list_new = editor.read_from_bin(bin_path_new, map_path, project_name, eq_name=eq_name)
    editor.write_to_config_file(struct_list_new, struct_name, cfg_filepath_write3)


def run_test2():
    editor = EqEditor()
    # editor.get_valid_project_config()
    cfg_read_path = "./audio_eq_hw_dac_iir_cfg_classics.cfg"
    cfg_write_path = "./audio_eq_hw_dac_iir_cfg_classics.new.cfg"
    project_name = "ts6569a"
    eq_name = "经典原音"
    eq_index = "1"
    struct_name = "audio_eq_hw_dac_iir_cfg_classics"
    struct_list = editor.read_from_config_file(cfg_read_path)
    editor.write_to_config_file(struct_list, cfg_write_path, project_name, eq_name=eq_name)


if __name__ == '__main__':
    run_test2()